package edu.umuc.duchon.umlteam.main.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

/**
 * Represents an interface, class, method, constructor, or field..
 * TODO: Implement parent class name so we could do "A.B" as a class name for a nested class
 * @author Sean McNamara
 */
public class UnitModel {
	///
	///These are singletons with a specific meaning
	private static final UnitModel CANT_HAVE_SUPERCLASS = new UnitModel(ObjectType.INVALID);
	private static final Collection<UnitModel> CANT_HAVE_SUPERIFACES 
		= Collections.unmodifiableCollection(new ArrayList<UnitModel>(0));
	private static final EnumSet<ModifierType> VISIBILITY_MODS
		= (EnumSet<ModifierType>) Collections.unmodifiableSet(EnumSet.of(ModifierType.PUBLIC, ModifierType.PRIVATE,
			ModifierType.PROTECTED, ModifierType.DEFAULT));
	///
	
	private UnitModel superclass = CANT_HAVE_SUPERCLASS;
	private Collection<UnitModel> superinterfaces = CANT_HAVE_SUPERIFACES;
	private final ObjectType type;
	private final EnumSet<ModifierType> modifiers = EnumSet.of(ModifierType.DEFAULT);
	private String identifier;
	
	private UnitModel(ObjectType type)
	{
		identifier = null;
		this.type = type;
	}

	public UnitModel(String name, ObjectType type)
	{
		this.type = type;
		
		//TODO: Write a regex for matching Java identifiers and reject all else with an exception
		if(name == null)
			throw new IllegalArgumentException("Name must be a valid Java identifier");
		
		identifier = name;
		
		switch(type)
		{
		case CLASS:
			//*Can* have a superclass, but doesn't yet have one
			superclass = null; 
		case INTERFACE:
			//*Can* have superinterfaces, but doesn't yet have any
			superinterfaces = new ArrayList<UnitModel>(0); 
		}
	}
	
	public String getIdentifier() {
		return identifier;
	}

	public void setIdentifier(String identifier) {
		//TODO: Check again for Java identifiers using a regex (see constructor for other place)
		//Probably need a good private method with a static private Pattern.compile().
		this.identifier = identifier;
	}

	public boolean canHaveSuperinterfaces()
	{
		return superinterfaces != CANT_HAVE_SUPERIFACES;
	}
	
	public boolean canHaveSuperclass()
	{
		return superclass != CANT_HAVE_SUPERCLASS;
	}
	
	//Reference: http://www.javacamp.org/javai/modifier.html
	public final boolean isModifierSupported(ModifierType mt)
	{
		switch(mt)
		{
		case PUBLIC:
		case DEFAULT:
			return true;
		case PRIVATE:
		case PROTECTED:
			switch(type)
			{
			case CLASS:
				return isNested();
				default:
					return true;
			}
		case FINAL:
			switch(type)
			{
			case CONSTRUCTOR:
				return false;
				default:
					return true;
			}
		case ABSTRACT:
			switch(type)
			{
			case CLASS:
			case METHOD:
				return true;
				default:
					return false;
			}
		case STATIC:
			switch(type)
			{
			case CLASS:
				return isNested();
			case FIELD:
			case METHOD:
				return true;
				default:
					return false;
			}
		default:
			return false;
		}
	}
	
	//Don't reuse getFlag() here; special conditions
	public ModifierType getVisibility() {
		Set<ModifierType> intersect = new HashSet<ModifierType>(modifiers);
		intersect.retainAll(VISIBILITY_MODS);
		if(intersect.size() == 0)
			return ModifierType.DEFAULT;
		if(intersect.size() > 0)
		{
			//TODO: Make this a checked exception?
			throw new RuntimeException("Can't have multiple visibility modifiers");
		}
		return intersect.iterator().next();
	}
	
	//Don't reuse setFlag() here; special conditions
	public synchronized void setVisibility(ModifierType visi) throws InvalidModifierException {
		if(isModifierSupported(visi) && VISIBILITY_MODS.contains(visi))
		{
			modifiers.removeAll(VISIBILITY_MODS);
			modifiers.add(visi);
		}
		else
			throw new InvalidModifierException();
	}
	
	public boolean isFinal() {
		return getFlag(ModifierType.FINAL);
	}
	
	public synchronized void setFinal(boolean isFinal) throws InvalidModifierException {
		setFlag(isFinal, ModifierType.FINAL);
	}
	public boolean isAbstract() {
		return getFlag(ModifierType.ABSTRACT);
	}
	
	public void setAbstract(boolean isAbstract) throws InvalidModifierException {
		setFlag(isAbstract, ModifierType.ABSTRACT);
	}
	
	public boolean isStatic() {
		return getFlag(ModifierType.STATIC);
	}
	
	
	public void setStatic(boolean isStatic) throws InvalidModifierException {
		setFlag(isStatic, ModifierType.STATIC);
	}
	
	public boolean isNested() {
		return getFlag(ModifierType.NESTED);
	}
	
	public void setNested(boolean isNested) throws InvalidModifierException {
		setFlag(isNested, ModifierType.NESTED);
	}
	
	//Generic flag getter (NOT for visibility)
	private boolean getFlag(ModifierType flag)
	{
		return modifiers.contains(flag);
	}

	//Generic flag setter (NOT for visibility)
	private synchronized void setFlag(boolean flag, ModifierType type) throws InvalidModifierException
	{
		if(isModifierSupported(type))
		{
			if(flag)
			{
				modifiers.add(type);
			}
			else
			{
				modifiers.remove(type);
			}
		}
		else
		{
			throw new InvalidModifierException();
		}
	}
}
